﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
using Red.CommUtils;
using Red.CommUtils.Application;
using Red.EngineData.Protocol.Interop;

namespace Red.Controls
{
    /// <summary>
    /// Stateless application monitor.
    /// </summary>
    public sealed class AppMonitor
    {
        /// <summary>
        /// On engine crash event, gives callstack as string.
        /// </summary>
        public event Action<string> OnCrash;

        /// <summary>
        /// On engine freeze event, gives delay in milliseconds as int.
        /// </summary>
        public event Action OnFreeze;

        /// <summary>
        /// On engine is fine, no freeze, no connection lost, no crash.
        /// </summary>
        public event Action OnFine;

        /// <summary>
        /// On connection with engine lost.
        /// </summary>
        public event Action OnConnectionLost;

        /// <summary>
        /// Gives samples with received bytes.
        /// </summary>
        public event Action<DateTime, ulong> OnTrackBytesReceived;

        /// <summary>
        /// Gives samples with sent bytes.
        /// </summary>
        public event Action<DateTime, ulong> OnTrackBytesSent;

        /// <summary>
        /// Gives debug entry which contains informations about interop operation
        /// </summary>
        public event Action<InteropDebugEntry> NewInteropDebugEntry;

        /// <summary>
        /// Enables/Disables app monitor.
        /// </summary>
        public bool Enabled
        {
            get { return _timer.Enabled; }
            set { _timer.Enabled = value; _sampleTimer.Enabled = value; }
        }

        /// <summary>
        /// Channel which is used for communication with app monitor service.
        /// </summary>
        private IChannel _channel;

        /// <summary>
        /// For monitoring statistics and interop execution
        /// </summary>
        private InteropDebugger _interopDebugger;

        /// <summary>
        /// Timer for checking if app is alive.
        /// </summary>
        private System.Timers.Timer _timer;

        /// <summary>
        /// Timer for sampling network statistics.
        /// </summary>
        private System.Timers.Timer _sampleTimer;

        /// <summary>
        /// List of all app channels for tracking purposes.
        /// </summary>
        private List<IChannel> _allAppChannels;

        /// <summary>
        /// App handle to attach to.
        /// </summary>
        private IAppHandle _appHandle;

        /// <summary>
        /// Lazy initialization flag for interop debug console
        /// </summary>
        private bool _lazyInteropDebugConsoleEnabled;

        /// <summary>
        /// Total amount of data received by editor.
        /// </summary>
        public ulong TotalBytesReceived
        {
            get
            {
                ulong result = 0;
                _allAppChannels.ForEach( channel => result += channel.TotalBytesReceived );
                return result;
            }
        }

        /// <summary>
        /// Total amount of data sent by editor.
        /// </summary>
        public ulong TotalBytesSent
        {
            get
            {
                ulong result = 0;
                _allAppChannels.ForEach( channel => result += channel.TotalBytesSent );
                return result;
            }
        }

        /// <summary>
        /// Number of total actions handled so far.
        /// </summary>
        public ulong ActionsHandled
        {
            get
            {
                if ( _interopDebugger != null )
                    return _interopDebugger.Stats.ActionsHandled;
                return 0;
            }
        }

        /// <summary>
        /// Number of total actions out of order so far.
        /// </summary>
        public ulong ActionsOutOfOrder
        {
            get
            {
                if ( _interopDebugger != null )
                    return _interopDebugger.Stats.ActionsOutOfOrder;
                return 0;
            }
        }

        /// <summary>
        /// Number of total remote action exceptions so far.
        /// </summary>
        public ulong RemoteActionExceptions
        {
            get
            {
                if ( _interopDebugger != null )
                    return _interopDebugger.Stats.RemoteActionExceptions;
                return 0;
            }
        }

        /// <summary>
        /// Number of current actions waiting for response.
        /// </summary>
        public int ActionsWaitingForResponse
        {
            get
            {
                if ( _interopDebugger != null )
                    return _interopDebugger.Stats.ActionsWaitingForResponse;
                return 0;
            }
        }

        /// <summary>
        /// Minimum time between send interop expression and receive interop 
        /// </summary>
        public string MinRequestTime
        {
            get
            {
                if ( _interopDebugger != null )
                    return _interopDebugger.Stats.MinRequestTime.ToString( "F3" );
                return "";
            }
        }

        /// <summary>
        /// Maximum time between send interop expression and receive interop 
        /// </summary>
        public string MaxRequestTime
        {
            get
            {
                if ( _interopDebugger != null )
                    return _interopDebugger.Stats.MaxRequestTime.ToString( "F3" );
                return "";
            }
        }


        /// <summary>
        /// Constructs.
        /// </summary>
        public AppMonitor()
        {
            _channel = null;
            _timer = new System.Timers.Timer();
            _timer.Interval = 1000;
            _timer.Elapsed += OnTimedEvent;

            _sampleTimer = new System.Timers.Timer();
            _sampleTimer.Interval = 125;
            _sampleTimer.Elapsed += OnSampleTimedEvent;

            _lazyInteropDebugConsoleEnabled = false;
        }

        /// <summary>
        /// Attaches app monitor to given app channels.
        /// </summary>
        /// <param name="communicationChannel"></param>
        /// <param name="allAppChannels"></param>
        public void AttachToChannel( IChannel communicationChannel, List<IChannel> allAppChannels )
        {
            _channel = communicationChannel;
            _allAppChannels = allAppChannels;
        }

        /// <summary>
        /// Attaches app monitor to given interop debugger.
        /// </summary>
        /// <param name="actionProtocol"></param>
        public void AttachToInteropDebugger( InteropProtocol actionProtocol )
        {
            _interopDebugger = actionProtocol.InteropDebugger;
            if ( _lazyInteropDebugConsoleEnabled )
                EnableInteropDebugConsole();
        }

        /// <summary>
        /// Flush all pending interop debug entries or set lazy initialization if interop debug console not exist
        /// </summary>
        public void EnableInteropDebugConsole()
        {
            if ( _interopDebugger == null )
            {
                _lazyInteropDebugConsoleEnabled = true;
                return;
            }

            _interopDebugger.NewInteropDebugEntry += OnNewInteropDebugEntry;
            _interopDebugger.FlushPendingDebugEntries();
        }

        /// <summary>
        /// Attach to app handle for getting additional statistics.
        /// </summary>
        /// <param name="appHandle"></param>
        public void AttachToAppHandle( IAppHandle appHandle )
        {
            _appHandle = appHandle;
        }

        /// <summary>
        /// Fired every sample timer interval.
        /// </summary>
        /// <param name="source"></param>
        /// <param name="e"></param>
        private void OnSampleTimedEvent( object source, System.Timers.ElapsedEventArgs e )
        {
            TrackConnection( e.SignalTime );
        }

        /// <summary>
        /// Fired every time interval.
        /// </summary>
        /// <param name="source"></param>
        /// <param name="e"></param>
        private void OnTimedEvent( object source, System.Timers.ElapsedEventArgs e )
        {
            if ( Monitor.TryEnter( _channel ) )
            {
                try
                {
                    CheckAppIsAlive( e.SignalTime );
                }
                finally
                {
                    Monitor.Exit( _channel );
                }
            }
        }

        /// <summary>
        /// Checks for freezes, crash, connection lost etc.
        /// </summary>
        /// <param name="signalTime"></param>
        private void CheckAppIsAlive( DateTime signalTime )
        {
            if ( _channel == null )
            {
                Debug.WriteLine( "[AppMonitor] {0} Monitor is not attached to any channel.", signalTime );
                return;
            }

            if ( IsConnectionLost() )
            {
                if ( CheckForCrashLog() )
                {
                    Enabled = false;
                    Debug.WriteLine( "[AppMonitor] {0} Application crashed.", signalTime );

                    string crashLog = ReadCrashLog();

                    if ( OnCrash != null )
                        OnCrash( crashLog );
                }
                else
                {
                    Enabled = false;
                    Debug.WriteLine( "[AppMonitor] {0} Connection with application lost.", signalTime );
                    if ( OnConnectionLost != null )
                        OnConnectionLost();
                }
            }
            else if ( IsFreeze() )
            {
                Debug.WriteLine( "[AppMonitor] {0} Application freeze.", signalTime );
                if ( OnFreeze != null )
                    OnFreeze();
            }
            else
            {
                if ( OnFine != null )
                    OnFine();
            }
        }

        /// <summary>
        /// Tracks amout of data sent and received.
        /// </summary>
        /// <param name="signalTime"></param>
        private void TrackConnection( DateTime signalTime )
        {
            if ( OnTrackBytesReceived != null )
            {
                OnTrackBytesReceived( signalTime, TotalBytesReceived );
            }

            if ( OnTrackBytesSent != null )
            {
                OnTrackBytesSent( signalTime, TotalBytesSent );
            }
        }

        /// <summary>
        /// Helper internal method.
        /// </summary>
        /// <returns></returns>
        private bool IsConnectionLost()
        {
            return !_channel.IsOpen();
        }

        /// <summary>
        /// Helper internal method.
        /// </summary>
        /// <returns></returns>
        private bool CheckForCrashLog()
        {
            return File.Exists( _appHandle.CrashFilePath );
        }

        /// <summary>
        /// Use it with CheckForCrashLog.
        /// </summary>
        /// <returns></returns>
        private string ReadCrashLog()
        {
            return File.ReadAllText( _appHandle.CrashFilePath );
        }

        /// <summary>
        /// Helper internal method.
        /// </summary>
        /// <returns></returns>
        private bool IsFreeze()
        {
            try
            {
                comm.IsAliveRequest message = new comm.IsAliveRequest();
                try
                {
                    comm.IsAliveResponse answer = _channel.SendAndWait( message, 1000 ) as comm.IsAliveResponse;
                    if ( answer != null )
                    {
                        return false;
                    }
                }
                catch ( CommunicationException )
                {
                    return true;
                }

                return true;
            }
            catch ( CommunicationException )
            {
                return true;
            }
        }

        /// <summary>
        /// Helper callback between interop debugger and interop app monitor
        /// </summary>
        /// <param name="newInteropDebugEntry">New interop debug entry with information about last interop operation.</param>
        private void OnNewInteropDebugEntry( InteropDebugEntry newInteropDebugEntry )
        {
            if ( NewInteropDebugEntry != null )
            {
                NewInteropDebugEntry( newInteropDebugEntry );
            }
        }
    }
}
